/*
  主要解释点：
  1、所有预览的动画（加入时间轴）的都可以通过baseControl的参数控制
  2、button参数的按钮互相采用三个空格符隔开！！！（包括解析）
 */
import './RotateFont.scss';
import React, { RefObject, useRef } from 'react';
import { animateObj, animationBaseProps } from '../Animation';
import CustomEase from 'gsap/CustomEase';
import gsap from 'gsap';
gsap.registerPlugin(CustomEase);

type fontDir = 'out' | 'in' | 'start'
type moveMent = 'float' | 'shake'
export type rotateFontRun = {
  words: string[]
  buttons: {
    words: string
    onClick: (e: MouseEvent) => void
  }[]
};
type fontScope = {
  minX: number,
  maxX: number,
  minY: number,
  maxY: number
}

const baseControl = {
  lineTime: {
    start: {
      wait: 1,
      in: 1,
      float: 1
    },
    in: {
      in: 0.4,
      shake: 1.6
    },
    out: {
      in: 0.8,
      shake: 0
    }
  },
  eachShake: 0.6,
  number: 900,
  radius: 7,
  fontWidth: 1200,
  fontHeight: 300,
  colors: ['rgba(179,148,157,${alpha})', 'rgba(255,246,120,${alpha})', 'rgba(255,94,140,${alpha})', 'rgba(55,169,204,${alpha})', 'rgba(23,142,179,${alpha})'],
  animateObj: (null as null | animateObj<rotateFontRun>),
  buttonMoveEventFn: (null as ((e: MouseEvent) => void) | null),
  buttonClickEventFn: (null as ((e: MouseEvent) => void) | null),
  outAnimateFn: (null as (() => void) | null)
};

class TimeLine {
  static positionTime = 0
  duration: number
  position: string
  delay?: number

  constructor(index: number, fontDir: fontDir, isOther: boolean = false, empty?: boolean) {
    if (empty) {
      this.duration = 0;
      this.position = '';
      return;
    }
    this.duration = TimeLine.createDuration(fontDir, isOther);
    this.position = baseControl.animateObj?.position + '+=' + TimeLine.positionTime;
    fontDir === 'start' && (this.delay = index * baseControl.lineTime.start.wait / baseControl.number);
  }

  createShakeTime(fontDir: fontDir): TimeLine {
    const empty = new TimeLine(0, 'start', false, true);
    switch (fontDir) {
      case 'in':
        empty.duration = baseControl.lineTime.in.shake;
        empty.position = this.position.split('+=')[0] + '+=' + (Number(this.position.split('+=')[1]) + baseControl.lineTime.in.in);
        return empty;
      case 'out':
        empty.duration = baseControl.lineTime.out.shake;
        empty.position = this.position.split('+=')[0] + '+=' + (Number(this.position.split('+=')[1]) + baseControl.lineTime.out.in);
        return empty;
      case 'start':
        empty.duration = baseControl.lineTime.start.float + baseControl.lineTime.start.wait - (this.delay || 0);
        empty.position = this.position.split('+=')[0] + '+=' + (Number(this.position.split('+=')[1]) + (this.delay || 0) + baseControl.lineTime.start.in);
        return empty;
    }
  }

  static createDuration(fontDir: fontDir, isOther: boolean): number {
    return isOther
      ? Object.values(baseControl.lineTime[fontDir]).reduce((all, item) => all + item, 0)
      : baseControl.lineTime[fontDir].in;
  }

  static addPositionTime(fontDir: fontDir) {
    TimeLine.positionTime += Object.values(baseControl.lineTime[fontDir]).reduce((all, item) => all + item, 0);
  }
}

class Circles {
  static circles: Circles[] = []
  x: number
  y: number
  baseX: number
  baseY: number
  alpha: number
  baseColor: string
  radius: number
  isFont?: boolean
  gsap?: GSAPTween
  originX?: number
  originY?: number

  constructor(props:{x:number, y: number, alpha: number, baseColor: string, radius: number, baseX: number, baseY: number }) {
    this.x = props.x;
    this.y = props.y;
    this.alpha = props.alpha;
    this.baseColor = props.baseColor;
    this.radius = props.radius;
    this.baseX = props.baseX;
    this.baseY = props.baseY;
    Circles.circles.push(this);
  }

  drawCircle(ctx:CanvasRenderingContext2D) {
    ctx.beginPath();
    ctx.arc(
      this.x,
      this.y,
      this.radius, 0, 2 * Math.PI
    );
    ctx.fillStyle = this.baseColor.replace('${alpha}', String(this.alpha));
    ctx.strokeStyle = this.baseColor.replace('${alpha}', '1');
    ctx.fill();
    // ctx.stroke();
  }
}

function createMoveGsap(target: HTMLCanvasElement) {
  const ctx = target.getContext('2d') as CanvasRenderingContext2D;
  const Circles = initCircles(ctx, target);
  animate();

  function animate() {
    ctx.clearRect(0, 0, target.width, target.height);
    Circles.forEach((circle) => circle.drawCircle(ctx));
    requestAnimationFrame(animate);
  }
}

function createFontGsap(target: HTMLCanvasElement, words: string, isButton: boolean = false) {
  const ctx = target.getContext('2d') as CanvasRenderingContext2D;
  const textPixels = createText(ctx, words, target);
  formText(textPixels, target, isButton, words);
}

function initCircles(ctx: CanvasRenderingContext2D, target: HTMLCanvasElement) {
  const circles = Array.from({ length: baseControl.number }).map((item, index) => {
    const circle = new Circles({
      x: target.offsetWidth / 2,
      y: target.offsetHeight / 2,
      baseX: target.offsetWidth * Math.random(),
      baseY: target.offsetHeight * Math.random(),
      alpha: 0,
      baseColor: baseControl.colors[Math.floor(index % baseControl.colors.length)],
      radius: baseControl.radius
    });
    gsapCircle(circle, 'start', new TimeLine(index, 'start'), target);
    return circle;
  });
  TimeLine.addPositionTime('start');
  return circles;
}

function createText(ctx: CanvasRenderingContext2D, words: string, target: HTMLCanvasElement) {
  let fontSize = baseControl.fontWidth / (words.length);
  if (fontSize > 120) fontSize = 120;

  ctx.clearRect(0, 0, target.width, target.height);
  // 设置字体
  ctx.font = '500 ' + fontSize + "px 'Source Sans Pro'";
  // 设置颜色
  ctx.fillStyle = '#ff0';
  // 设置水平对齐方式
  ctx.textAlign = 'center';
  // 绘制文字（参数：要写的字，x坐标，y坐标）
  ctx.fillText(words, baseControl.fontWidth / 2, baseControl.fontHeight / 2);

  const pix = ctx.getImageData(0, 0, baseControl.fontWidth, baseControl.fontHeight).data;

  const textPixels = [];
  for (let i = pix.length; i >= 0; i -= 4) {
    if (pix[i] !== 0) {
      const x = (i / 4) % baseControl.fontWidth;
      const y = Math.floor(Math.floor(i / baseControl.fontWidth) / 4);

      if ((x && x % 7 === 0) && (y && y % 7 === 0)) textPixels.push({ x: x, y: y });
    }
  }

  return textPixels;
}

function formText(textPixels: {x: number, y: number}[], target: HTMLCanvasElement, isButton: boolean, words: string) {
  const circles = Circles.circles;
  const fontScope = {
    minX: Infinity,
    maxX: -Infinity,
    minY: Infinity,
    maxY: -Infinity
  };

  for (let i = 0, l = circles.length; i < l; i++) {
    if (i < textPixels.length) {
      const coordinate = textPixels[i];

      fontScope.minX = Math.min(fontScope.minX, (target.offsetWidth - baseControl.fontWidth) / 2 + coordinate.x);
      fontScope.maxX = Math.max(fontScope.maxX, (target.offsetWidth - baseControl.fontWidth) / 2 + coordinate.x);
      fontScope.minY = Math.min(fontScope.minY, (target.offsetHeight - baseControl.fontHeight) / 2 + coordinate.y);
      fontScope.maxY = Math.max(fontScope.maxY, (target.offsetHeight - baseControl.fontHeight) / 2 + coordinate.y);

      circles[i].originX = (target.offsetWidth - baseControl.fontWidth) / 2 + coordinate.x;
      circles[i].originY = (target.offsetHeight - baseControl.fontHeight) / 2 + coordinate.y;
      circles[i].isFont = true;
      gsapCircle(circles[i], 'in', new TimeLine(i, 'in'), target, isButton);
    } else {
      createShakeTween(circles[i], new TimeLine(i, 'in', true), 'float', isButton);
    }
  }

  TimeLine.addPositionTime('in');
  isButton && (baseControl.buttonMoveEventFn = createButtonEvent(fontScope, words, 'move'));
  isButton && (baseControl.buttonClickEventFn = createButtonEvent(fontScope, words, 'click'));
}

function createButtonEvent(fontScope: fontScope, words: string, evnetType: 'move' | 'click') {
  // 将一个字符串空格转换为一个空字符串
  const wordsList = words.replace(/\s{3}/g, ' ').split('');
  // 计算每一个模块占了多少字符
  const eachX = (fontScope.maxX - fontScope.minX) / wordsList.length;
  let auxiliaryIndex = -1;

  function moveFn(e: MouseEvent) {
    if (
      e.clientX < fontScope.maxX &&
        e.clientX > fontScope.minX &&
        e.clientY < fontScope.maxY &&
        e.clientY > fontScope.minY
    ) {
      const chooseIndex = Math.floor((e.clientX - fontScope.minX) / eachX);
      if (auxiliaryIndex === chooseIndex) {
        return;
      }

      const chooseWords = wordsList[chooseIndex];
      if (chooseWords !== ' ') {
        auxiliaryIndex = chooseIndex;
        addMouseWordsGSAP('in', fontScope.minX + (chooseIndex + 1 / 2) * eachX, fontScope.maxY + 25);
        (e.target as HTMLCanvasElement).style.cursor = 'pointer';
      } else {
        auxiliaryIndex = -1;
        addMouseWordsGSAP('out');
        (e.target as HTMLCanvasElement).style.cursor = '';
      }
    } else if (auxiliaryIndex !== -1) {
      auxiliaryIndex = -1;
      addMouseWordsGSAP('out');
      (e.target as HTMLCanvasElement).style.cursor = '';
    }
  }

  function clickFn(e: MouseEvent) {
    if (
      e.clientX < fontScope.maxX &&
        e.clientX > fontScope.minX &&
        e.clientY < fontScope.maxY &&
        e.clientY > fontScope.minY
    ) {
      const chooseIndex = Math.floor((e.clientX - fontScope.minX) / eachX);

      if (chooseIndex % 2 === 0) {
        baseControl.animateObj?.run?.buttons && baseControl.animateObj?.run.buttons[chooseIndex / 2]?.onClick(e);
        chooseIndex === 0 && baseControl.outAnimateFn && baseControl.outAnimateFn();
        chooseIndex === 0 && baseControl.animateObj?.stopCall && baseControl.animateObj.stopCall();
        (e.target as HTMLCanvasElement).style.cursor = '';
      }
    }
  }

  const eventMap = {
    move: moveFn,
    click: clickFn
  };

  return eventMap[evnetType];
}

function addMouseWordsGSAP(dir: fontDir, x:number = 0, y: number = 0) {
  const circles = Circles.circles;
  Array.from({ length: 30 }).forEach((item, index) => {
    const circle = circles[circles.length - index - 1];
    circle.originX = x + Math.random() * 10;
    circle.originY = y + Math.random() * 10;
    foreverCircle(circle, dir);
  });
}

function fontOut() {
  const circles = Circles.circles;

  circles.forEach((circle, index) => {
    if (circle.isFont) {
      gsapCircle(circle, 'out', new TimeLine(index, 'out'));
      circle.isFont = false;
    } else {
      createShakeTween(circle, new TimeLine(index, 'out', true), 'float');
    }
  });

  TimeLine.addPositionTime('out');
}

function gsapCircle(c: Circles, fontDir: fontDir, timeline: TimeLine, target?: HTMLCanvasElement, isButton?: boolean) {
  if (fontDir === 'start' && target && timeline.delay) {
    baseControl.animateObj?.t1.to(c, {
      duration: timeline.duration,
      delay: timeline.delay,
      x: c.baseX,
      y: c.baseY,
      alpha: 0.2 + Math.random() * 0.5,
      ease: 'back.inOut(3)'
    }, timeline.position);

    createShakeTween(c, timeline.createShakeTime(fontDir), 'float');
  } else if (fontDir === 'in') {
    baseControl.animateObj?.t1.to(c, {
      duration: timeline.duration,
      x: c.originX,
      y: c.originY,
      ease: 'Quad.easeInOut',
      alpha: 1,
      radius: 3,
      onComplete: isButton ? function() {
        target && target.addEventListener('mousemove', (baseControl.buttonMoveEventFn as any));
        target && target.addEventListener('click', (baseControl.buttonClickEventFn as any));
      } : undefined
    }, timeline.position);

    createShakeTween(c, timeline.createShakeTime(fontDir), 'shake', isButton);
  } else if (fontDir === 'out') {
    baseControl.animateObj?.t1.to(c, {
      duration: timeline.duration,
      x: c.baseX,
      y: c.baseY,
      ease: 'Quad.easeInOut',
      alpha: 0.2 + Math.random() * 0.5,
      radius: 5
    }, timeline.position);

    createShakeTween(c, timeline.createShakeTime(fontDir), 'float');
  }
}

function createShakeTween(
  c:Circles,
  timeline: TimeLine,
  moveMent: moveMent,
  isButton?: boolean
) {
  if (moveMent === 'float') {
    baseControl.animateObj?.t1.to(c, {
      duration: timeline.duration,
      x: c.baseX + -20 * timeline.duration + Math.random() * 40 * timeline.duration,
      y: c.baseY + -20 * timeline.duration + Math.random() * 40 * timeline.duration,
      ease: 'Quad.easeInOut',
      alpha: 0.2 + Math.random() * 0.5,
      onComplete: isButton ? function() {
        foreverCircle(c, moveMent);
      } : undefined
    }, timeline.position);
  } else if (moveMent === 'shake') {
    const times = Math.floor(timeline.duration / baseControl.eachShake);
    Array.from({ length: times }).forEach((item, index) => {
      baseControl.animateObj?.t1.to(c, {
        duration: baseControl.eachShake,
        x: (c.originX || 0) + Math.random() * 3,
        y: (c.originY || 0) + Math.random() * 3,
        ease: CustomEase.create('custom', 'M0,0,C0,0,0.04,0.758,0.076,1,0.128,0.802,0.2,0,0.2,0,0.2,0,0.334,1,0.334,1,0.334,1,0.476,0,0.476,0,0.476,0,0.576,1,0.576,1,0.576,1,0.722,0,0.722,0,0.722,0,0.858,1,0.858,1,0.858,1,0.991,0,1,0'),
        onComplete: isButton ? function() {
          foreverCircle(c, moveMent);
        } : undefined
      }, timeline.position.split('+=')[0] + '+=' + (Number(timeline.position.split('+=')[1]) + index * baseControl.eachShake));
    });
  }
}

function foreverCircle(c: Circles, dir: moveMent|fontDir) {
  if (c.gsap) c.gsap.kill();
  if (dir === 'in') {
    c.gsap = gsap.to(c, 0.4, { x: c.originX, y: c.originY, ease: 'Quad.easeInOut', alpha: 1, radius: 5,
      onComplete: function() {
        foreverCircle(c, 'shake');
      } });
  } else if (dir === 'out') {
    c.gsap = gsap.to(c, 0.8, { x: window.innerWidth * Math.random(), y: window.innerHeight * Math.random(), ease: 'Quad.easeInOut', alpha: 0.2 + Math.random() * 0.5,
      onComplete: function() {
        foreverCircle(c, 'float');
      } });
  } else if (dir === 'float') {
    c.gsap = gsap.to(c, { duration: 5 + Math.random() * 3.5, x: c.x + -100 + Math.random() * 200, y: c.y + -100 + Math.random() * 200, ease: 'Quad.easeInOut', alpha: 0.2 + Math.random() * 0.5,
      onComplete: function() {
        foreverCircle(c, dir);
      } });
  } else {
    c.gsap = gsap.to(c, { duration: 0.05, x: (c.originX || 0) + Math.random() * 3, y: (c.originY || 0) + Math.random() * 3, ease: 'Quad.easeInOut',
      onComplete: function() {
        foreverCircle(c, dir);
      } });
  }
}

function removeForeverGSAP(target: HTMLCanvasElement) {
  const circles = Circles.circles;
  return function() {
    circles.forEach((circle) => circle.gsap && circle.gsap.kill());
    target.removeEventListener('mousemove', (baseControl.buttonMoveEventFn as any));
    target.removeEventListener('click', (baseControl.buttonClickEventFn as any));
  };
}

function createRemoveAnimate(target: HTMLCanvasElement) {
  const circles = Circles.circles;
  return function() {
    circles.forEach((circle) => {
      circle.gsap && circle.gsap.kill();
      gsap.to(circle, {
        x: target.offsetWidth / 2,
        y: target.offsetHeight / 2,
        alpha: 0
      });
    });
  };
}

const RotateFont: React.FC<animationBaseProps<rotateFontRun>> = (props) => {
  props.animateObj.t1.addLabel('Process', 0);
  const boxRef: RefObject<HTMLDivElement> = useRef<HTMLDivElement>(null);
  const stageRef: RefObject<HTMLCanvasElement> = useRef<HTMLCanvasElement>(null);
  const textRef: RefObject<HTMLCanvasElement> = useRef<HTMLCanvasElement>(null);
  baseControl.animateObj = props.animateObj;

  React.useEffect(() => {
    stageRef.current && (stageRef.current.width = boxRef.current?.offsetWidth || 0);
    stageRef.current && (stageRef.current.height = boxRef.current?.offsetHeight || 0);

    textRef.current && (textRef.current.width = boxRef.current?.offsetWidth || 0);
    textRef.current && (textRef.current.height = boxRef.current?.offsetHeight || 0);

    props.animateObj && (props.animateObj.stopCall = removeForeverGSAP(textRef.current as HTMLCanvasElement));
    baseControl.outAnimateFn = createRemoveAnimate(textRef.current as HTMLCanvasElement);

    createMoveGsap(stageRef.current as HTMLCanvasElement);
    props.animateObj.run?.words.forEach((item) => {
      createFontGsap(textRef.current as HTMLCanvasElement, item);
      fontOut();
    });

    createFontGsap(textRef.current as HTMLCanvasElement, props.animateObj.run?.buttons.reduce((all, item) => all ? `${all}   ${item.words}` : item.words, '') || '', true);

    return function cleanup() {
      console.log('组件被卸载componentWillUnmount');
    };
  },
  []);

  return <div className={'rotate-box'} ref={boxRef}>
    <canvas ref={stageRef} className={'f-center'}/>
    <canvas ref={textRef} className={'f-center rotate-text'}/>
  </div>;
};

export default RotateFont;
